using System;
using System.Security.Cryptography;
using System.Text;

namespace PreguntaAlExperto.ObjectMapper.Helper
{
    public sealed class PasswordGenerator
    {
        #region Constants
        private const int DEFAULT_MIN_LENGTH = 6;
        private const int DEFAULT_MAX_LENGTH = 10;
        private const int UPPER_BOUND_DIGITS = 61;
        #endregion

        #region Fields
        private RNGCryptoServiceProvider rng;
        private int minSize;
        private int maxSize;
        private bool hasRepeating;
        private bool hasConsecutive;
        private bool hasSymbols;
        private string exclusionSet;
        private char[] pwdCharArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/?".ToCharArray();
        #endregion

        #region Properties
        public string Exclusions
        {
            get { return this.exclusionSet; }
            set { this.exclusionSet = value; }
        }

        public int MinLength
        {
            get { return this.minSize; }
            set
            {
                this.minSize = value;
                if (PasswordGenerator.DEFAULT_MIN_LENGTH > this.minSize)
                {
                    this.minSize = PasswordGenerator.DEFAULT_MIN_LENGTH;
                }
            }
        }

        public int MaxLength
        {
            get { return this.maxSize; }
            set
            {
                this.maxSize = value;
                if (this.minSize >= this.maxSize)
                {
                    this.maxSize = PasswordGenerator.DEFAULT_MAX_LENGTH;
                }
            }
        }

        public bool HasSymbols
        {
            get { return this.hasSymbols; }
            set { this.hasSymbols = value; }
        }

        public bool RepeatCharacters
        {
            get { return this.hasRepeating; }
            set { this.hasRepeating = value; }
        }

        public bool ConsecutiveCharacters
        {
            get { return this.hasConsecutive; }
            set { this.hasConsecutive = value; }
        }
        #endregion

        #region Constructors
        public PasswordGenerator() 
        {
            this.MinLength 				= DEFAULT_MIN_LENGTH;
            this.MaxLength 				= DEFAULT_MAX_LENGTH;
            this.ConsecutiveCharacters	= false;
            this.RepeatCharacters 		= false;
            this.HasSymbols             = false;
            this.Exclusions             = null;

            rng = new RNGCryptoServiceProvider();
        }
        #endregion

        #region Private Members
        private int GetCryptographicRandomNumber(int lowerLimit, int upperLimit)
        {
            if (lowerLimit < 0 || lowerLimit >= upperLimit)
                throw new ArgumentException();
 
            uint urndnum;   
            byte[] rndnum = new Byte[4];   
            if (lowerLimit == upperLimit-1)  
            {
                return lowerLimit;
            }
                                                              
            uint xcludeRndBase = (uint.MaxValue - (uint.MaxValue%(uint)(upperLimit-lowerLimit)));   
            
            do 
            {      
                rng.GetBytes(rndnum);      
                urndnum = System.BitConverter.ToUInt32(rndnum,0);      
            } while (urndnum >= xcludeRndBase);   
            
            return (int)(urndnum % (upperLimit-lowerLimit)) + lowerLimit;
        }

        private char GetRandomCharacter()
        {            
            int upperBound = pwdCharArray.GetUpperBound(0);

            if ( false == this.HasSymbols )
            {
                upperBound = PasswordGenerator.UPPER_BOUND_DIGITS;
            }

            int randomCharPosition = GetCryptographicRandomNumber(pwdCharArray.GetLowerBound(0), upperBound);

            char randomChar = pwdCharArray[randomCharPosition];

            return randomChar;
        }
        #endregion

        #region Public Members
        public string Generate()
        {
            // Pick random length between minimum and maximum   
            int pwdLength = GetCryptographicRandomNumber(this.MinLength, this.MaxLength);

            StringBuilder pwdBuffer = new StringBuilder();
            pwdBuffer.Capacity = this.MaxLength;

            // Generate random characters
            char lastCharacter, nextCharacter;

            // Initial dummy character flag
            lastCharacter = nextCharacter = '\n';

            for ( int i = 0; i < pwdLength; i++ )
            {
                nextCharacter = GetRandomCharacter();

                if ( false == this.ConsecutiveCharacters )
                {
                    while ( lastCharacter == nextCharacter )
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                if ( false == this.RepeatCharacters )
                {
                    string temp = pwdBuffer.ToString();
                    int duplicateIndex = temp.IndexOf(nextCharacter);
                    while ( -1 != duplicateIndex )
                    {
                        nextCharacter = GetRandomCharacter();
                        duplicateIndex = temp.IndexOf(nextCharacter);
                    }
                }

                if ( ( null != this.Exclusions ) )
                {
                    while ( -1 != this.Exclusions.IndexOf(nextCharacter) )
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                pwdBuffer.Append(nextCharacter);
                lastCharacter = nextCharacter;
            }

            if ( null != pwdBuffer )
            {
                return pwdBuffer.ToString();
            }
            else
            {
                return String.Empty;
            }
        }
        #endregion
    }
}